En dybdeanalyse av CSS @apply-regelen. Lær hva den var, hvorfor den ble avviklet, og utforsk moderne alternativer for mixin-applikasjon og stilkomposisjon.
CSS @apply-regelen: Oppgangen og fallet for native mixins og moderne alternativer
I det stadig utviklende landskapet for webutvikling er jakten på renere, mer vedlikeholdbar og gjenbrukbar kode en konstant. I årevis har utviklere lent seg på CSS-preprosessorer som Sass og Less for å bringe programmatisk kraft til stilark. En av de mest elskede funksjonene fra disse verktøyene er mixin – en måte å definere en gjenbrukbar blokk med CSS-deklarasjoner. Dette førte til et naturlig spørsmål: kunne vi ha denne kraftige funksjonen native i CSS? En stund så svaret ut til å være ja, og navnet var @apply.
@apply-regelen var et lovende forslag som hadde som mål å bringe mixin-lignende funksjonalitet direkte inn i nettleseren, ved å utnytte kraften i CSS Custom Properties. Det lovet en fremtid der vi kunne definere gjenbrukbare stilutdrag i ren CSS og anvende dem hvor som helst, til og med oppdatere dem dynamisk med JavaScript. Men hvis du er en utvikler i dag, vil du ikke finne @apply i noen stabil nettleser. Forslaget ble til slutt trukket tilbake fra den offisielle CSS-spesifikasjonen.
Denne artikkelen er en omfattende utforskning av CSS @apply-regelen. Vi vil reise gjennom hva den var, det kraftige potensialet den hadde for stilkomposisjon, de komplekse årsakene til at den ble avviklet, og viktigst av alt, de moderne, produksjonsklare alternativene som løser de samme problemene i dagens utviklingsøkosystem.
Hva var CSS @apply-regelen?
I kjernen var @apply-regelen designet for å ta et sett med CSS-deklarasjoner lagret i en egendefinert egenskap (custom property) og "anvende" dem innenfor en CSS-regel. Dette tillot utviklere å lage det som i hovedsak var "egenskapssamlinger" eller "regelsett" som kunne gjenbrukes på tvers av flere selektorer, og dermed legemliggjøre prinsippet om "Don't Repeat Yourself" (DRY).
Konseptet var bygget på CSS Custom Properties (ofte kalt CSS-variabler). Mens vi vanligvis bruker egendefinerte egenskaper til å lagre enkeltverdier som en farge (--brand-color: #3498db;) eller en størrelse (--font-size-md: 16px;), utvidet forslaget for @apply deres kapasitet til å inneholde hele blokker med deklarasjoner.
Den foreslåtte syntaksen
Syntaksen var enkel og intuitiv for alle som er kjent med CSS. Først ville du definere en egendefinert egenskap som inneholdt en blokk med CSS-deklarasjoner, omsluttet av krøllparenteser {}.
:root {
--primary-button-styles: {
background-color: #007bff;
color: #ffffff;
border: 1px solid transparent;
padding: 0.5rem 1rem;
font-size: 1rem;
border-radius: 0.25rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
};
}
Deretter, innenfor hvilken som helst CSS-regel, kunne du bruke @apply at-regelen for å injisere hele den stilblokken:
.btn-primary {
@apply --primary-button-styles;
}
.form-submit-button {
@apply --primary-button-styles;
margin-top: 1rem; /* Du kunne fortsatt legge til andre stiler */
}
I dette eksempelet ville både .btn-primary og .form-submit-button arve det komplette settet med stiler definert i --primary-button-styles. Dette var et betydelig avvik fra standard var()-funksjonen, som bare kan erstatte en enkelt verdi i en enkelt egenskap.
Sentrale tiltenkte fordeler
- Gjenbrukbarhet av kode: Den mest åpenbare fordelen var å eliminere repetisjon. Vanlige mønstre som knappestiler, kortoppsett eller varselbokser kunne defineres én gang og brukes overalt.
- Forbedret vedlikeholdbarhet: For å oppdatere utseendet på alle primære knapper, ville du bare trengt å redigere den egendefinerte egenskapen
--primary-button-styles. Endringen ville da forplante seg til hvert element der den ble brukt. - Dynamisk temastyring: Fordi den var basert på egendefinerte egenskaper, kunne disse mixinene endres dynamisk med JavaScript, noe som muliggjorde kraftige kjøretids-temafunksjoner som preprosessorer (som opererer på kompileringstidspunktet) ikke kan tilby.
- Bygge bro over gapet: Den lovet å bringe en høyt elsket funksjon fra preprosessor-verdenen inn i native CSS, og redusere avhengigheten av byggeverktøy for denne spesifikke funksjonaliteten.
Løftet om @apply: Native mixins og stilkomposisjon
Potensialet til @apply strakte seg langt utover enkel gjenbruk av stiler. Det låste opp to kraftige konsepter for CSS-arkitektur: native mixins og deklarativ stilkomposisjon.
Et native svar på preprosessor-mixins
I årevis har Sass vært gullstandarden for mixins. La oss sammenligne hvordan Sass oppnår dette med hvordan @apply var ment å fungere.
En typisk Sass Mixin:
@mixin flexible-center {
display: flex;
justify-content: center;
align-items: center;
}
.hero-banner {
@include flexible-center;
height: 100vh;
}
.modal-content {
@include flexible-center;
flex-direction: column;
}
Ekvivalenten med @apply:
:root {
--flexible-center: {
display: flex;
justify-content: center;
align-items: center;
};
}
.hero-banner {
@apply --flexible-center;
height: 100vh;
}
.modal-content {
@apply --flexible-center;
flex-direction: column;
}
Syntaksen og utvikleropplevelsen var bemerkelsesverdig like. Den viktigste forskjellen lå imidlertid i utførelsen. Sass @mixin blir prosessert under et byggetrinn og produserer statisk CSS. @apply-regelen ville blitt prosessert av nettleseren under kjøring. Denne forskjellen var både dens største styrke og, som vi skal se, dens endelige fall.
Deklarativ stilkomposisjon
@apply ville ha gjort det mulig for utviklere å bygge komplekse komponenter ved å sette sammen mindre, enkeltstående stilutdrag. Se for deg å bygge et UI-komponentbibliotek der du har grunnleggende blokker for typografi, layout og utseende.
:root {
--typography-body: {
font-family: 'Inter', sans-serif;
font-size: 16px;
line-height: 1.5;
color: #333;
};
--card-layout: {
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
};
--theme-light: {
background-color: #ffffff;
border: 1px solid #ddd;
};
--theme-dark: {
background-color: #2c3e50;
border: 1px solid #444;
color: #ecf0f1;
};
}
.article-card {
@apply --typography-body;
@apply --card-layout;
@apply --theme-light;
}
.user-profile-card.dark-mode {
@apply --typography-body;
@apply --card-layout;
@apply --theme-dark;
}
Denne tilnærmingen er svært deklarativ. CSS-en for .article-card angir tydelig dens sammensetning: den har brødtekst-typografi, et kortoppsett og et lyst tema. Dette gjør koden lettere å lese og resonnere om.
Den dynamiske superkraften
Den mest overbevisende funksjonen var dens dynamikk under kjøring. Siden --card-theme kunne være en vanlig egendefinert egenskap, kunne du bytte ut hele regelsett med JavaScript.
/* CSS */
.user-profile-card {
@apply --typography-body;
@apply --card-layout;
@apply var(--card-theme, --theme-light); /* Bruk et tema, med lys som standard */
}
/* JavaScript */
const themeToggleButton = document.getElementById('theme-toggle');
themeToggleButton.addEventListener('click', () => {
const root = document.documentElement;
const isDarkMode = root.style.getPropertyValue('--card-theme') === '--theme-dark';
if (isDarkMode) {
root.style.setProperty('--card-theme', '--theme-light');
} else {
root.style.setProperty('--card-theme', '--theme-dark');
}
});
Dette hypotetiske eksempelet viser hvordan du kunne veksle en komponent mellom et lyst og mørkt tema ved å endre en enkelt egendefinert egenskap. Nettleseren ville da måtte re-evaluere @apply-regelen og bytte ut en stor del av stilene i sanntid. Dette var en utrolig kraftig idé, men den antydet også den enorme kompleksiteten som boblet under overflaten.
Den store debatten: Hvorfor ble @apply fjernet fra CSS-spesifikasjonen?
Med en så overbevisende visjon, hvorfor forsvant @apply? Beslutningen om å fjerne den ble ikke tatt lett på. Den var resultatet av lange og komplekse diskusjoner innenfor CSS Working Group (CSSWG) og blant nettleserleverandører. Årsakene kokte ned til betydelige problemer med ytelse, kompleksitet og de grunnleggende prinsippene i CSS.
1. Uakseptable ytelsesimplikasjoner
Dette var den primære årsaken til dens fall. CSS er designet for å være utrolig rask og effektiv. Nettleserens render-motor kan parse stilark, bygge CSSOM (CSS Object Model) og anvende stiler på DOM i en høyt optimalisert sekvens. @apply-regelen truet med å knuse disse optimaliseringene.
- Parsing og validering: Når en nettleser møter en egendefinert egenskap som
--main-color: blue;, trenger den ikke å validere verdien `blue` før den faktisk brukes i en egenskap som `color: var(--main-color);`. Med@applymåtte imidlertid nettleseren parse og validere en hel blokk med vilkårlige CSS-deklarasjoner inne i en egendefinert egenskap. Dette er en mye tyngre oppgave. - Kaskadekompleksitet: Den største utfordringen var å finne ut hvordan
@applyville samhandle med kaskaden. Når du bruker@applypå en blokk med stiler, hvor passer disse stilene inn i kaskaden? Har de samme spesifisitet som regelen de er i? Hva skjer hvis en@apply-anvendt egenskap senere blir overstyrt av en annen stil? Dette skapte et problem med "sent brudd" i kaskaden som var beregningsmessig dyrt og vanskelig å definere konsekvent. - Uendelige løkker og sirkulære avhengigheter: Det introduserte muligheten for sirkulære referanser. Hva om
--mixin-aanvendte--mixin-b, som igjen anvendte--mixin-a? Å oppdage og håndtere disse tilfellene under kjøring ville legge betydelig overhead til CSS-motoren.
I hovedsak krevde @apply at nettleseren måtte gjøre en betydelig mengde arbeid som normalt håndteres av byggeverktøy på kompileringstidspunktet. Å utføre dette arbeidet effektivt under kjøring for hver stil-rekalkulering ble ansett som for kostbart fra et ytelsesperspektiv.
2. Brudd på garantiene i kaskaden
CSS-kaskaden er et forutsigbart, om enn noen ganger komplekst, system. Utviklere stoler på reglene for spesifisitet, arv og kilderekkefølge for å resonnere om stilene sine. @apply-regelen introduserte et nivå av indireksjon som gjorde denne resonneringen mye vanskeligere.
Vurder dette scenarioet:
:root {
--my-mixin: {
color: blue;
};
}
div {
@apply --my-mixin; /* fargen er blå */
color: red; /* fargen er nå rød */
}
Dette virker enkelt nok. Men hva om rekkefølgen ble snudd?
div {
color: red;
@apply --my-mixin; /* Overstyrer dette den røde fargen? */
}
CSSWG måtte bestemme: oppfører @apply seg som en "shorthand"-egenskap som utvides på stedet, eller oppfører den seg som et sett med deklarasjoner som injiseres med sin egen kilderekkefølge? Denne tvetydigheten undergravde den grunnleggende forutsigbarheten til CSS. Det ble ofte beskrevet som "magi" – et begrep utviklere bruker om atferd som ikke er lett å forstå eller feilsøke. Å introdusere denne typen magi i kjernen av CSS var en betydelig filosofisk bekymring.
3. Syntaks- og parsing-utfordringer
Selve syntaksen, selv om den virket enkel, skapte problemer. Å tillate vilkårlig CSS inne i verdien til en egendefinert egenskap betydde at CSS-parseren måtte være mye mer kompleks. Den måtte håndtere nestede blokker, kommentarer og potensielle feil i selve egenskapsdefinisjonen, noe som var et betydelig avvik fra hvordan egendefinerte egenskaper var designet for å fungere (å holde på det som i hovedsak er en streng frem til substitusjon).
Til syvende og sist var konsensusen at kostnadene ved ytelse og kompleksitet langt overgikk fordelene for utviklerbekvemmelighet, spesielt når andre løsninger allerede eksisterte eller var i horisonten.
Arven etter @apply: Moderne alternativer og beste praksis
Drømmen om gjenbrukbare stilutdrag i CSS er langt fra død. Problemene som @apply hadde som mål å løse er fortsatt høyst reelle, og utviklermiljøet har siden omfavnet flere kraftige, produksjonsklare alternativer. Her er hva du bør bruke i dag.
Alternativ 1: Mestre CSS Custom Properties (den tiltenkte måten)
Den mest direkte, native løsningen er å bruke CSS Custom Properties til deres opprinnelige formål: å lagre enkle, gjenbrukbare verdier. I stedet for å lage en mixin for en knapp, lager du et sett med egendefinerte egenskaper som definerer knappens tema. Denne tilnærmingen er kraftig, ytelseseffektiv og fullt støttet av alle moderne nettlesere.
Eksempel: Bygge en komponent med Custom Properties
:root {
--btn-padding-y: 0.5rem;
--btn-padding-x: 1rem;
--btn-font-size: 1rem;
--btn-border-radius: 0.25rem;
--btn-transition: color .15s ease-in-out, background-color .15s ease-in-out;
}
.btn {
/* Strukturelle stiler */
display: inline-block;
padding: var(--btn-padding-y) var(--btn-padding-x);
font-size: var(--btn-font-size);
border-radius: var(--btn-border-radius);
transition: var(--btn-transition);
cursor: pointer;
text-align: center;
border: 1px solid transparent;
}
.btn-primary {
/* Temastyring via egendefinerte egenskaper */
--btn-bg: #007bff;
--btn-color: #ffffff;
--btn-hover-bg: #0056b3;
background-color: var(--btn-bg);
color: var(--btn-color);
}
.btn-primary:hover {
background-color: var(--btn-hover-bg);
}
.btn-secondary {
--btn-bg: #6c757d;
--btn-color: #ffffff;
--btn-hover-bg: #5a6268;
background-color: var(--btn-bg);
color: var(--btn-color);
}
.btn-secondary:hover {
background-color: var(--btn-hover-bg);
}
Denne tilnærmingen gir deg temastyrte, vedlikeholdbare komponenter ved hjelp av native CSS. Strukturen er definert i .btn, og temaet (den delen du kanskje ville ha puttet i en @apply-regel) styres av egendefinerte egenskaper som er scopet til modifikatorer som .btn-primary.
Alternativ 2: Utility-First CSS (f.eks. Tailwind CSS)
Utility-first-rammeverk som Tailwind CSS har tatt konseptet stilkomposisjon til sin logiske konklusjon. I stedet for å lage komponentklasser i CSS, setter du sammen stiler direkte i HTML-en din ved hjelp av små, enkeltstående hjelpeklasser (utility classes).
Interessant nok har Tailwind CSS sitt eget @apply-direktiv. Det er avgjørende å forstå at dette IKKE er den native CSS @apply. Tailwinds @apply er en funksjon som kjører på byggetidspunktet og fungerer innenfor sitt økosystem. Den leser hjelpeklassene dine og kompilerer dem til statisk CSS, og unngår dermed alle ytelsesproblemene ved kjøring som det native forslaget hadde.
Eksempel: Bruk av Tailwinds @apply
/* I din CSS-fil som prosesseres av Tailwind */
.btn-primary {
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded hover:bg-blue-700;
}
/* I din HTML */
<button class="btn-primary">
Primary Button
</button>
Her tar Tailwinds @apply en liste med hjelpeklasser og lager en ny komponentklasse, .btn-primary. Dette gir den samme utvikleropplevelsen med å lage gjenbrukbare sett med stiler, men gjør det trygt på kompileringstidspunktet.
Alternativ 3: CSS-in-JS-biblioteker
For utviklere som jobber i JavaScript-rammeverk som React, Vue eller Svelte, tilbyr CSS-in-JS-biblioteker (f.eks. Styled Components, Emotion) en annen kraftig måte å oppnå stilkomposisjon på. De bruker JavaScripts egen komposisjonsmodell for å bygge stiler.
Eksempel: Mixins i Styled Components (React)
import styled, { css } from 'styled-components';
// Definer en mixin ved hjelp av en template literal
const buttonBaseStyles = css`
background-color: #007bff;
color: #ffffff;
border: 1px solid transparent;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
cursor: pointer;
`;
// Lag en komponent og anvend mixin-en
const PrimaryButton = styled.button`
${buttonBaseStyles}
&:hover {
background-color: #0056b3;
}
`;
// En annen komponent som gjenbruker de samme grunnstilene
const SubmitButton = styled.input.attrs({ type: 'submit' })`
${buttonBaseStyles}
margin-top: 1rem;
`;
Dette utnytter den fulle kraften i JavaScript for å lage gjenbrukbare, dynamiske og scopede stiler, og løser DRY-problemet innenfor det komponentbaserte paradigmet.
Alternativ 4: CSS-preprosessorer (Sass, Less)
La oss ikke glemme verktøyene som startet det hele. Sass og Less er fortsatt utrolig kraftige og mye brukt. Deres mixin-funksjonalitet er moden, funksjonsrik (de kan ta imot argumenter) og helt pålitelig fordi de, i likhet med Tailwinds @apply, opererer på kompileringstidspunktet.
For mange prosjekter, spesielt de som ikke er bygget på et tungt JavaScript-rammeverk, er en preprosessor fortsatt den enkleste og mest effektive måten å håndtere komplekse, gjenbrukbare stiler på.
Konklusjon: Lærdommer fra @apply-eksperimentet
Historien om CSS @apply-regelen er en fascinerende casestudie i utviklingen av webstandarder. Den representerer et dristig forsøk på å bringe en elsket utviklerfunksjon inn i den native plattformen. At den til slutt ble trukket tilbake var ikke en fiasko for ideen, men et bevis på CSS Working Groups forpliktelse til ytelse, forutsigbarhet og den langsiktige helsen til språket.
De viktigste lærdommene for utviklere i dag er:
- Omfavn CSS Custom Properties for verdier, ikke regelsett. Bruk dem til å lage kraftige temasystemer og opprettholde designkonsistens.
- Velg riktig verktøy for komposisjon. Problemet
@applyprøvde å løse – stilkomposisjon – håndteres best av dedikerte verktøy som opererer på byggetidspunktet (som Tailwind CSS eller Sass) eller innenfor en komponents kontekst (som CSS-in-JS). - Forstå "hvorfor" bak webstandarder. Å vite hvorfor en funksjon som
@applyble avvist, gir oss en dypere forståelse for kompleksiteten i nettleserutvikling og de grunnleggende prinsippene i CSS, som kaskaden.
Selv om vi kanskje aldri vil se en native @apply-regel i CSS, lever ånden videre. Ønsket om en mer modulær, komponentdrevet og DRY tilnærming til styling har formet de moderne verktøyene og beste praksisene vi bruker hver dag. Webplattformen fortsetter å utvikle seg, med funksjoner som CSS Nesting, @scope og Cascade Layers som gir nye, native måter å skrive mer organisert og vedlikeholdbar CSS på. Reisen mot en bedre styling-opplevelse pågår, og lærdommene fra eksperimenter som @apply er det som baner vei fremover.